/*
 * Licensed to the Tai Ping Jin Ke
 *
 * Copyright (c) 2022 .
 * All rights reserved.
 * 项目名称：太平金科-通用能力封装层-执行http网络请求
 * 版权说明：本软件属太平金融科技服务(上海)有限公司所有，在未获得太平金融科技服务(上海)有限公司正式授权情况下，任何企业和个人，不能获取、阅读、安装、传播本软件涉及的任何受知识产权保护的内容。
 */
package com.taipingframework.http.template;

import cn.hutool.core.lang.TypeReference;
import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeType;
import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
import com.jayway.jsonpath.Option;
import com.jayway.jsonpath.spi.json.JacksonJsonNodeJsonProvider;
import com.taipingframework.utility.http.HttpResponseEntity;
import com.taipingframework.utility.http.ServiceConfigInfo;
import com.taipingframework.utility.http.ServiceConfigInfo.ServiceResponseConfigInfo;
import com.taipingframework.utility.http.ServiceConfigInfo.ParamProperty;
import com.taipingframework.utility.http.constant.BatchReturnEnum;
import com.taipingframework.utility.http.constant.DataParseTypeEnum;
import com.taipingframework.utility.http.constant.HttpServiceFieldConstant;
import com.taipingframework.utility.http.constant.ResponseBodyDataTypeEnum;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * <p>
 * 基于Http协议实现网络数据交互的模板服务类
 * </p>
 *
 * @author 周孝兵
 * @since 2021-12-02
 */
@Component
public class HttpResponseTemplate {

    /**
     * HTTP请求响应模板的实现逻辑
     *
     * @param serviceConfigInfo  自定义动态执行HTTP请求的参数配置信息
     * @param httpResponseEntity HTTP响应结果
     * @return 根据HTTP响应参数配置信息解析后得到的数据对象
     */
    public Map<String, Object> doResponse(ServiceConfigInfo serviceConfigInfo, HttpResponseEntity httpResponseEntity) {
        // 1.0 获取 响应内容的解析配置
        ServiceResponseConfigInfo httpResponseConfig = serviceConfigInfo.getServiceResponseConfigInfo();

        // 1.1 响应内容的数据格式：
        //     J - JSON格式(比较常见)，
        //     T - Text文本格式(暂时不支持，部分奇葩接口也许就是那么令人意外)，
        //     X - XML格式(暂时不支持，古老的WebService服务会返回这种格式)
        ResponseBodyDataTypeEnum responseBodyDataType = httpResponseConfig.getResponseBodyDataType();

        // 1.2 响应内容的解析方式：
        //     [A] - AES解析，
        //     [B64] - BASE64解码，
        //     [A,B64] - 先AES解密再BASE64解码，
        //     [B64,A] - 先BASE64解码再AES解密，
        //     [-] - 不做任何处理
        DataParseTypeEnum dataParseType = httpResponseConfig.getDataParseType();

        // 1.3 映射业务数据的根路径，依据该路径的字符特征可判断是否在解析json数组
        // 如果需解析的业务数据格式为json数组，则形如  $.result.list[*]
        // 如果需解析的业务数据格式为json对象，则形如  $.result.object
        String rootMapPath = httpResponseConfig.getRootMapPath();

        // 1.4 是否分批返回：Y-是，N-否
        // 分批返回则意味着要请求的网络服务是以分页形式提供数据的，那么业务数据肯定是以json数组的形式返回的，要获取所有的业务数据就必须通过分页参数发送多次请求
        // 当然，业务数据量较少的情况下，业务数据只需通过json数组的形式返回一次即可。
        // 如果需解析的业务数据格式为json对象，则该参数一定为N；但是如果需解析的业务数据格式为json数组，则该参数既可以为Y也可以为N，这是编码逻辑。
        BatchReturnEnum batchReturn = httpResponseConfig.getBatchReturn();

        // 1.5 数据完整性校验
        if (responseBodyDataType != ResponseBodyDataTypeEnum.JSON) {
            throw new RuntimeException("不支持非JSON格式的数据处理");
        }
        if (dataParseType == null) {
            throw new RuntimeException("未声明响应内容的解析方式");
        }
        if (rootMapPath == null || rootMapPath.length() <= 0) {
            throw new RuntimeException("映射业务数据的根路径为空，无法对网络服务的响应数据进行解析");
        }
        if (batchReturn == null) {
            throw new RuntimeException("未说明是否分批返回需要的业务数据，无法对网络服务的响应数据进行正确解析");
        }

        // 2.0 获取响应内容的解析配置
        List<ParamProperty> bodyParam = httpResponseConfig.getMappingParamConfig();

        // 3.0 声明适用于DSM系统的数据结构
        Map<String, Object> dataObject = new HashMap<>();

        // 3.1 创建JsonPath的配置对象，在处理json文档时，使用自定义的配置而非默认配置，能为我们的逻辑处理提供更大的便利
        Configuration config = Configuration.builder()
                .jsonProvider(new JacksonJsonNodeJsonProvider())
                .options(Option.ALWAYS_RETURN_LIST, Option.SUPPRESS_EXCEPTIONS)
                .build();
        // Object document = Configuration.defaultConfiguration().jsonProvider().parse(responseContent);
        // JsonPath.read(document, jPath);

        // 3.2 创建Json文档的上下文对象，说明：由于我们需要多次对responseContent进行解析，不要直接使用 JsonPath.read(jsonStr, jPath);
        DocumentContext docContext = JsonPath.using(config)
                .parse(httpResponseEntity.getResponseBodyJson());

        ArrayNode bizArrayNode;
        // 3.3 分批返回
        if (batchReturn == BatchReturnEnum.YES && StringUtils.isNotEmpty(httpResponseConfig.getCurrentPageKey())) {
            // 响应内容当前第几页数据
            bizArrayNode = docContext.read(httpResponseConfig.getCurrentPageKey());
            String currentPage = valueOf(bizArrayNode);
            dataObject.put(HttpServiceFieldConstant.CURRENT_PAGE, currentPage);
        }
        if (batchReturn == BatchReturnEnum.YES && StringUtils.isNotEmpty(httpResponseConfig.getTotalPageKey())) {
            // 响应内容总页数
            bizArrayNode = docContext.read(httpResponseConfig.getTotalPageKey());
            String totalPage = valueOf(bizArrayNode);
            dataObject.put(HttpServiceFieldConstant.TOTAL_PAGE, totalPage);
        }

        // 3.4 按照 响应内容的解析配置 解析json，填充Map对象
        bizArrayNode = docContext.read(rootMapPath);
        int size = bizArrayNode.size();
        if (size <= 0) {
            return JSONUtil.toBean(httpResponseEntity.getResponseBodyJson(), new TypeReference<Map<String, Object>>() {
            }, false);
        }

        Map<String, Object> record;
        JsonNode bizObjectNode;
        if (rootMapPath.endsWith("[*]") || rootMapPath.endsWith("]")) {
            // 3.4.1 作为json数组解析
            List<Object> recordList = new ArrayList<>();
            for (int i = 0; i < size; i++) {
                record = new HashMap<>();
                bizObjectNode = bizArrayNode.get(i);
                for (ParamProperty item : bodyParam) {
                    record.put(item.getTargetFieldName(),
                            bizObjectNode.getNodeType() == JsonNodeType.NULL
                                    ? null
                                    : bizObjectNode.get(item.getSourceParamPath())
                    );
                }
                // 如果没有配置响应映射关系，就把json数组填充到dsm_result(例如JSON数组元素是纯文本，就不需要配置响应映射关系)
                recordList.add(bodyParam.size() > 0
                        ? record
                        : bizObjectNode.getNodeType() == JsonNodeType.NULL
                        ? null
                        : bizObjectNode.asText()
                );
            }
            dataObject.put(HttpServiceFieldConstant.DATA_RESULT, recordList);
        } else {
            // 3.4.2 作为json对象解析
            record = new HashMap<>();
            bizObjectNode = bizArrayNode.get(0);
            for (ParamProperty item : bodyParam) {
                record.put(item.getTargetFieldName(),
                        bizObjectNode.getNodeType() == JsonNodeType.NULL
                                ? null
                                : bizObjectNode.get(item.getSourceParamPath())
                );
            }
            // 如果没有配置响应映射关系，就把json对象填充到dsm_result(例如纯文本，就不需要配置响应映射关系)
            dataObject.put(HttpServiceFieldConstant.DATA_RESULT,
                    bodyParam.size() > 0
                            ? record
                            : bizObjectNode.getNodeType() == JsonNodeType.NULL
                            ? null
                            : bizObjectNode.asText()
            );
        }

        // 3.5 返回适用于DSM系统的数据结构
        dataObject.put(HttpServiceFieldConstant.ERR_CODE, 0);
        dataObject.put(HttpServiceFieldConstant.ERR_MSG, "ok");
        return dataObject;
    }

    /**
     * 读取JsonNode节点的值
     */
    private String valueOf(ArrayNode arrayNode) {
        return arrayNode.getNodeType() != JsonNodeType.NULL && arrayNode.size() > 0 && arrayNode.get(0).getNodeType() != JsonNodeType.NULL
                ? arrayNode.get(0).asText()
                : null;
    }
}
